package com.xdhl.fu.fi.controller;

import com.alibaba.fastjson.JSONObject;
import com.xdhl.fu.common.core.constant.event.FlightStockSyncConstants;
import com.xdhl.fu.common.core.constant.event.reservation.ReservationConstants;
import com.xdhl.fu.common.core.utils.StringUtils;
import com.xdhl.fu.common.core.web.controller.BaseController;
import com.xdhl.fu.common.core.web.domain.AjaxResult;
import com.xdhl.fu.common.redis.service.RedisService;
import com.xdhl.fu.common.security.utils.SecurityUtils;
import com.xdhl.fu.fi.repository.MGFlightScheduleRepository;
import com.xdhl.fu.fi.repository.MGReservationRepository;
import com.xdhl.fu.fi.rocketmq.MsgProducer;
import com.xdhl.fu.fi.utils.CacheKeyPrefix;
import com.xdhl.fu.flight.info.api.domain.vo.FlightScheduleVO;
import com.xdhl.fu.flight.info.api.mongodb.domain.MGFlightSchedule;
import com.xdhl.fu.flight.info.api.mongodb.domain.MGReservation;
import org.apache.rocketmq.common.message.Message;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 航班预订控制器
 *
 * @author 朱力维
 * @time 2022/6/10 17:45
 */
@RestController
@RequestMapping("/flight/reservation")
public class FlightReservationController extends BaseController {

    @Resource
    private RedisService redisService;

    @Resource
    private MsgProducer msgProducer;

    @Resource
    private CacheKeyPrefix cacheKeyPrefix;

    @Resource
    private MGFlightScheduleRepository mgFlightScheduleRepository;

    @Resource
    private MGReservationRepository mgReservationRepository;

    /**
     * 航班预订
     *
     * @param flightScheduleVO 航班预订相关信息
     * @return 预订结果
     */
    @Transactional
    @PostMapping
    public AjaxResult reservation(@RequestBody FlightScheduleVO flightScheduleVO) throws Exception {
        // 已扣除的座位库存数量
        int headTotal = 0, businessTotal = 0, economyTotal = 0;
        // 预订结果
        boolean result = true;

        Map<String, MGFlightSchedule> flightScheduleMap = new HashMap<>();

        for (String flightScheduleDataId : flightScheduleVO.getFlights()) {
            // 获取航班信息
            MGFlightSchedule mgFlightSchedule = mgFlightScheduleRepository.findById(flightScheduleDataId).get();
            if (null != mgFlightSchedule) {
                // 计算缓存前缀
                String seatCachePrefix = cacheKeyPrefix.getSeatCachePrefix(mgFlightSchedule.getDataId());
                flightScheduleMap.put(seatCachePrefix, mgFlightSchedule);
                for (Map<String, String> passenger : flightScheduleVO.getPassengers()) {
                    String passengerDataId = passenger.get("dataId");
                    String flightClass = passenger.get("flightClass");
                    String countKey = seatCachePrefix + flightClass;
                    // 通过redis缓存获取库存数量结果并判断余票是否充足
                    String countStr = redisService.getStringValue(countKey);
                    if (StringUtils.isNotBlank(countStr) && Integer.valueOf(countStr) > 0) {
                        // 有余票则执行原子减操作并执行
                        redisService.decr(countKey, 1);
                        switch (flightClass) {
                            case "A":
                                headTotal++;
                                break;
                            case "B":
                                businessTotal++;
                                break;
                            case "C":
                                economyTotal++;
                                break;
                        }
                    } else {
                        result = false;
                        break;
                    }
                }
            } else {
                return AjaxResult.error("预订失败。");
            }
        }

        if (result) {
            for (Map.Entry<String, MGFlightSchedule> entry : flightScheduleMap.entrySet()) {
                // 异步库存同步
                this.sendSeatStockUpdateEvent(entry.getKey(), entry.getValue().getDataId());
                // 创建订单事件
                this.doReservation(entry.getValue(), flightScheduleVO);
            }
            return AjaxResult.success("预订成功！");
        } else {
            for (Map.Entry<String, MGFlightSchedule> entry : flightScheduleMap.entrySet()) {
                // 回滚库存
                this.stockBack(flightScheduleVO, entry.getKey(), headTotal, businessTotal, economyTotal);
            }
            return AjaxResult.error("余票不足。");
        }
    }

    /**
     * 单程票预订
     *
     * @param flightSchedule   航班相关信息信息
     * @param flightScheduleVO 预订相关信息
     */
    private void doReservation(MGFlightSchedule flightSchedule, FlightScheduleVO flightScheduleVO) throws Exception {
        MGReservation reservation = new MGReservation();
        int suffix = redisService.incr(StringUtils.orderSerialNumberKey(), 1);
        reservation.setDataId(StringUtils.orderSerialNumberKey() + suffix);
        reservation.setFlightSchedule(flightSchedule);
        mgReservationRepository.save(reservation);
        JSONObject data = new JSONObject();
        data.put("dataId", reservation.getDataId());
        data.put("username", SecurityUtils.getUsername());
        data.put("passengers", flightScheduleVO.getPassengers());
        data.put("orderSerialNumber", reservation.getDataId());
        this.sendCreateOrderEvent(data);
    }

    /**
     * 发送订单创建事件
     *
     * @param data 相关数据
     * @throws Exception
     */
    private void sendCreateOrderEvent(JSONObject data) throws Exception {
        Message message = new Message();
        message.setTopic(ReservationConstants.RESERVATION_CREATE);
        message.setBody(data.toJSONString().getBytes(StandardCharsets.UTF_8));
        message.setTags("");
        msgProducer.getMqProducer().send(message);
    }

    /**
     * 发送更新库存事件消息
     *
     * @param seatCachePrefix 缓存前缀
     * @param scheduleDataId  航班计划数据ID
     * @throws Exception
     */
    private void sendSeatStockUpdateEvent(String seatCachePrefix, String scheduleDataId) throws Exception {
        JSONObject data = new JSONObject();
        data.put("cacheKey", seatCachePrefix);
        data.put("scheduleDataId", scheduleDataId);
        Message message = new Message();
        message.setTopic(FlightStockSyncConstants.TOPIC);
        message.setTags("");
        message.setBody(data.toJSONString().getBytes(StandardCharsets.UTF_8));
        msgProducer.getMqProducer().send(message);
    }

    /**
     * 已扣除的座位库存进行回滚
     *
     * @param flightScheduleVO 航班预订的相关信息
     * @param seatCachePrefix  座位库存缓存前缀
     * @param headTotal        已扣除的头等舱座位库存数量
     * @param businessTotal    已扣除的商务舱座位库存数量
     * @param economyTotal     已扣除的经济舱座位库存数量
     */
    private void stockBack(FlightScheduleVO flightScheduleVO, String seatCachePrefix, int headTotal, int businessTotal, int economyTotal) {
        for (Map<String, String> passenger : flightScheduleVO.getPassengers()) {
            String flightClass = passenger.get("flightClass");

            String countKey = seatCachePrefix + flightClass;
            switch (flightClass) {
                case "A":
                    redisService.incr(countKey, headTotal);
                    break;
                case "B":
                    redisService.incr(countKey, businessTotal);
                    break;
                case "C":
                    redisService.incr(countKey, economyTotal);
                    break;
            }
        }
    }

}
